﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MySql.Data.MySqlClient;
namespace Region
{
    public class DBObjectAttribute : Attribute
    {
        public String Table { get; set; }
        public String PrimaryKeyPole { get; set; }        
    }
    public class DBColumnAttribute: Attribute
    {
        public String PoleName { get; set; }
        public EPoleType PoleType {get; set;}
    }
    public interface IReadWriteObject
    {
        //Object getPole(String PoleName);
        //bool setPole(String PoleName, Object Value);
        IDictionary<String, Object> getPoles(IList<String> Poles=null);
        void setPoles(IDictionary<String, Object> Poles);
        EPoleType getPoleType(String PoleName);
        bool poleAccess(String PoleName);
    };
    public class DBObject : IReadWriteObject
    {     
      public bool Valid{get{if (!m_read) read();return m_valid;}}
      protected Engine m_engine;
      private bool m_read;
      private bool m_valid=false;
      private String m_table; // Имя таблици
      private String m_primaryKeyPole; // имя ключевого поля
      private Object m_PKValue; // Значение ключевого поля
      protected Boolean m_deleteEnable = false; // разрешить удалять объект
      public Object PKValue { get { return m_PKValue; } }
      private Dictionary<String, Object> d_Poles;// Считанные поля
      private Dictionary<String, EPoleType> d_PolesType; // тип данных поля
      private Dictionary<String, Object> d_newPoles; // Изменённые поля
      //private Dictionary<String, bool> d_editPoles; // Изменённые поля
      private void constructor(Engine engine, String Table, String PrimaryKeyPole, Object PKValue, IDictionary<String, Object> Fields = null)
      {
          m_engine = engine;
          m_primaryKeyPole = PrimaryKeyPole;
          m_table = Table;
          m_PKValue = PKValue;
          m_read = false;
          m_deleteEnable = false;
          //d_editPoles = new Dictionary<string, bool>();
          d_newPoles = new Dictionary<string, Object>();
          d_PolesType = new Dictionary<String, EPoleType>();
          DBaddPoles();
          if (Fields != null)
              read(Fields);
          else
              read();
      }
      public DBObject(Engine engine, String Table, String PrimaryKeyPole, Object PKValue, IDictionary<String, Object> Fields = null)
      {
          constructor(engine, Table, PrimaryKeyPole, PKValue, Fields);
      }
      #region DBObjectAttribute
      public DBObject(Engine engine, Object PKValue, IDictionary<String, Object> Poles = null)
      {
          var attribute = Attribute.GetCustomAttribute(this.GetType(), typeof(Region.DBObjectAttribute)) as Region.DBObjectAttribute;          
          var Table = attribute.Table;
          var PrimaryKeyPole = attribute.PrimaryKeyPole;
          if(Table==null)
              Table = this.GetType().Name;
          if(PrimaryKeyPole==null)
              PrimaryKeyPole = Table+"ID";
          constructor(engine, Table, PrimaryKeyPole, PKValue, Poles);
      }
      protected void DBaddPoles()
        {
            
            var columnType = typeof(Region.DBColumnAttribute);
            foreach (var m in this.GetType().GetProperties())
            {
                var attributes = m.GetCustomAttributes(columnType, false);
                if (attributes.Length > 0)
                {
                    var attribute = (Region.DBColumnAttribute)attributes[0];
                    if (attribute.PoleType != EPoleType.Void)
                        d_PolesType[m.Name] = attribute.PoleType;
                    else
                        switch (m.PropertyType.FullName)
                        {
                            case "System.Boolean": d_PolesType[m.Name] = EPoleType.Bool; break;
                            case "System.DateTime": d_PolesType[m.Name] = EPoleType.Date; break;
                            case "System.Int32": d_PolesType[m.Name] = EPoleType.Int32; break;
                            case "System.Int64": d_PolesType[m.Name] = EPoleType.Int64; break;
                            case "System.String": d_PolesType[m.Name] = EPoleType.String; break;
                            default:
                                break;
                        }
                    
                }
            }
        }
      protected virtual void DBread()
      {
          var columnType = typeof(Region.DBColumnAttribute);
          foreach (var m in this.GetType().GetProperties())
          {
              var attributes = m.GetCustomAttributes(columnType, false);
              if (attributes.Length > 0)
              {
                  var attribute = (Region.DBColumnAttribute)attributes[0];
                  var PoleName = attribute.PoleName;
                  if (PoleName == null)
                      PoleName = m.Name;
                  m.SetValue(this, getPole(PoleName), null);
              }
          }
      }
      protected virtual void DBwrite()
      {
          var columnType = typeof(Region.DBColumnAttribute);
          foreach (var m in this.GetType().GetProperties())
          {
              var attributes = m.GetCustomAttributes(columnType, false);
              if (attributes.Length > 0)
              {
                  var attribute = (Region.DBColumnAttribute)attributes[0];
                  var PoleName = attribute.PoleName;
                  if (PoleName == null)
                      PoleName = m.Name;
                  setPole(PoleName, m.GetValue(this, null));
              }
          }
      }
      #endregion
      public IDictionary<String, Object> getPoles(IList<String> Poles=null)
      {
          DBwrite();
          var rPoles = new Dictionary<String, Object>();
          foreach (var p in Poles!=null?Poles:d_PolesType.Keys.ToList())
              rPoles[p] = getPole(p);
          return rPoles;
      }
      public void setPoles(IDictionary<String, Object> Poles)
      {
          DBwrite();
          foreach (var p in Poles)
              setPole(p.Key, p.Value);
          DBread();
      }
      /// <summary>Проверка вводимого значания</summary><param name="PoleName">Имя поля</param><param name="Value">Значание</param><returns>true - если значание допустимо</returns>        
      public virtual bool poleCheck(String PoleName, Object Value) { return true; }
      /// <summary>Пороверка доступа к полю</summary><param name="PoleName">Имя поля</param><returns>true - есть доступ к полю</returns>
      public virtual bool poleAccess(String PoleName) { return true; }
      public virtual bool isPole(String PoleName)
      {
        return d_PolesType.ContainsKey(PoleName);
      }
      /// <summary>Получить значение поля</summary><param name="PoleName">Имя поля</param><returns>значание поля</returns>
      protected virtual Object getPole(String PoleName)
      {
          if (!m_read) read();
          if (!d_PolesType.ContainsKey(PoleName)) return null;
          if (d_newPoles.ContainsKey(PoleName)) return d_newPoles[PoleName];            
          return d_Poles[PoleName];
      }
      public virtual EPoleType getPoleType(String PoleName) { return d_PolesType[PoleName];}

      /// <summary>
      /// Присвоить значение полю
      /// </summary>
      /// <param name="PoleName">Имя поля</param>
      /// <param name="Value">Значание(см. EPoleType)</param>
      /// <returns></returns>       
      protected virtual bool setPole(String PoleName, Object Value)
      {
          Boolean ErrorAccess = false;
          Boolean Access = poleAccess(PoleName);
          Boolean Check = poleCheck(PoleName, Value);
          if (d_PolesType.ContainsKey(PoleName))
          {
              //Проверка изменения поля              
              Object newValue;
              Object readValue = d_Poles[PoleName];
              Boolean equal = true;
              switch (d_PolesType[PoleName])
              {
                  case EPoleType.Bool: newValue = (bool)Value; equal = ((bool)Value == (bool)readValue); break;
                  case EPoleType.Int32: newValue = (Int32)Value; equal = ((Int32)Value == (Int32)readValue); break;
                  case EPoleType.Int64: newValue = (Int64)Value; equal = ((Int64)Value == (Int64)readValue); break;
                  case EPoleType.Date: newValue = (RegionDate)Value; equal = ((RegionDate)Value == (RegionDate)readValue); break;
                  case EPoleType.RegionDate: newValue = (RegionDate)Value; equal = ((RegionDate)Value == (RegionDate)readValue); break;
                    //newValue = (Value == null) ? new DateTime(1970, 1, 1) : (DateTime)Value; break;
                  case EPoleType.String: newValue = (String)Value; equal = ((String)Value == (String)readValue); break;
                  default: return false;
              }
              
              if (equal)
              {
                  if (d_newPoles.ContainsKey(PoleName))
                      if (Access && Check)
                          d_newPoles.Remove(PoleName);
                      else
                          ErrorAccess = true;
              }
              else
                  if (Access && Check)
                      d_newPoles[PoleName] = newValue; 
                  else
                      ErrorAccess = true;             
              
          }
          if (ErrorAccess)
              throw new ExceptionRegionAccessIsDenied(m_engine,"Нет доступа на изменение поля",this, PoleName);
          return true;
      }
      protected void addPoles(EPoleType PoleType, String[] Poles) { foreach (String p in Poles) d_PolesType[p] = PoleType; }
      public bool Refresh() { return read(); }

      public virtual bool read(IDictionary<String, Object> Fields)
      {
        m_read = false;
        m_valid = false;
        d_Poles = new Dictionary<String, Object>();
        d_newPoles = new Dictionary<string, Object>();// стереть изменённые значения
        //try
        {
          foreach (KeyValuePair<String, EPoleType> E in d_PolesType)
          {
            switch (E.Value)
            {
                case EPoleType.Bool: d_Poles[E.Key] = (Boolean)Fields[E.Key]; break;
                case EPoleType.Int32: d_Poles[E.Key] = (Int32)Fields[E.Key]; break;
                case EPoleType.Int64: d_Poles[E.Key] = (Int64)Fields[E.Key]; break;
                case EPoleType.RegionDate: d_Poles[E.Key] = ((Int32)Fields[E.Key] == 0) ? (new RegionDate()) : (new RegionDate((Int32)Fields[E.Key])); break;
                case EPoleType.Date: d_Poles[E.Key] = (Fields[E.Key] == null) ? (new RegionDate()) : ((RegionDate)Fields[E.Key]); break;
                case EPoleType.TimeStamp: d_Poles[E.Key] = Fields[E.Key]; break;
                case EPoleType.String: d_Poles[E.Key] = Engine.RegionStringToString((String)Fields[E.Key]); break;
              default: throw new ExceptionRegionEPoleType(m_engine,"DBObject тип данных при чтении не реализован",E.Value);
              }
            }
          }
          m_read = true;
          DBread();
          //catch (Exception) { return false; }          
          m_valid = true;          
          return m_read;
      }
      protected virtual bool isRead(){if (!m_read) return read();return false;}
      public virtual bool read(MySqlConnection RegionDB=null)
      {          
          SQLResult r = m_engine.SQLSelect(String.Format("SELECT * FROM {0} WHERE {1}=@PrimaryKeyValue LIMIT 1", m_table, m_primaryKeyPole), new SQLParam() { { "@PrimaryKeyValue", m_PKValue } },RegionDB);
          if (r.Count == 0) {m_valid = false; return false;}
          return read(r[0]);
      }
      public virtual bool cancel()
      {
          d_newPoles = new Dictionary<string, Object>();
          return true;
      }
      public virtual bool delete()
      {       
        if (!m_deleteEnable) return false;
        SQLParam P = new SQLParam();
        P["@PrimaryKey"] = PKValue;
        m_engine.SQLEdit("DELETE FROM " + m_table + " WHERE " + m_primaryKeyPole + "=@PrimaryKey LIMIT 1", P);
        return true;
      }

      public virtual bool save()
      {
          DBwrite();
          if (d_newPoles.Count > 0)
          {
              String[] PolesNames = new String[d_newPoles.Count];
              Int16 N = 0;
              foreach (KeyValuePair<String, Object> E in d_newPoles) PolesNames[N++] = E.Key + "=@" + E.Key;
              SQLParam P = new SQLParam();
              P["@PrimaryKey"] = PKValue;
              foreach (KeyValuePair<String, Object> E in d_newPoles)
              {
                  switch (d_PolesType[E.Key])
                  {
                      case EPoleType.Bool: P["@" + E.Key] = (bool)E.Value; break;
                      case EPoleType.Int32: P["@" + E.Key] = (Int32)E.Value; break;
                      case EPoleType.Int64: P["@" + E.Key] = (Int64)E.Value; break;
                      case EPoleType.RegionDate: P["@" + E.Key] = ((RegionDate)E.Value).Empty?0:((RegionDate)E.Value).getRegionDate(); break;
                      case EPoleType.Date: if (((RegionDate)E.Value).Empty)P["@" + E.Key] = null; else P["@" + E.Key] = ((RegionDate)E.Value).DateTime; break;
                      case EPoleType.String: P["@" + E.Key] = Engine.StringToRegionString((String)E.Value); break;
                      default: return false;
                  }
                  d_Poles[E.Key] = E.Value;
              }
              m_engine.SQLEdit("UPDATE " + m_table + " SET " + String.Join(", ", PolesNames) + " WHERE " + m_primaryKeyPole + "=@PrimaryKey LIMIT 1", P);
              d_newPoles = new Dictionary<string, Object>();
              //d_editPoles = new Dictionary<string, bool>();

          }
          return true;
      }
      public virtual bool isEdit(){return (d_newPoles.Count > 0);}
      public virtual void showEditor() { }
    }
    public class ExceptionRegionAccessIsDenied : ExceptionRegion
    {
        IReadWriteObject m_RWObject;
        String m_PoleName;
        public IReadWriteObject RWObject{get{return m_RWObject;}}
        public String PoleName{get{return PoleName;}}
        public ExceptionRegionAccessIsDenied(Engine engine,String message, IReadWriteObject RWObject,String PoleName):base(engine,message) { m_RWObject = RWObject; m_PoleName=PoleName; }
    }
}
